Pub/Sub with the go-redis client#

Conceptually, Redis Pub/Sub is quite simple. Here’s how it works with the go-redis client:

Redis Pub/Sub with the go-redis client
  • Line 1: Producers send data to a Redis channel.

  • Lines 3–4: Consumers subscribe to the channel to receive messages using Subscribe and receive messages via a Go channel.

  • Line 6: Here, we use Unsubscribe and close the connection.

The application used in this lesson is a simplified version of a chat service.

Application overview#

The application uses a combination of Redis Pub/Sub and WebSocket to allow a horizontally scalable architecture. The application hosts a WebSocket server endpoint that clients (users) can connect to using a WebSocket client.

Here’s the high-level application flow:

  1. A user connects to an endpoint provided by the application. If successful, this creates a WebSocket connection between the client and application (server).

  2. When the user sends a chat message, it’s relayed over the WebSocket connection to the application.

  3. The application publishes this message to the Redis channel.

  4. Because the application is also subscribed to this channel, it receives this message and sends the application information about all the connected users (via the WebSocket connection that was established initially).

Note that the server also acts as a Pub/Sub client. We create a Redis Pub/Sub channel for broadcasting chat messages. The application sends data to this Redis channel and also receives data from the channel via a Pub/Sub subscription.

High-level service architecture
High-level service architecture

Use cases#

In this application, users can do the following actions:

  • Join the chat service

  • Send messages

  • Receive messages

  • Leave the chat service

What is WebSocket?#

WebSocket is a protocol defined by RFC 6455, which allows full-duplex communication between a client and server over a single TCP connection.

Full-duplex communication implies that:

  • Either the client or server can send messages.

  • Messages can be exchanges simultaneously

It’s an improvement over other communication patterns like long polling, which is used by HTTP-based solutions and is heavily used in building real-time applications.

The role of Redis Pub/Sub#

It’s possible to build this solution using only WebSocket and not Redis Pub/Sub. But it will only work with a single instance of the application. This implies that it won’t be possible to run multiple instances of the application for scalability.

In a scaled-out architecture, the users connected to the chat service might be associated with any of the application instances via the WebSocket connection, as shown in the diagram above. Without Redis Pub/Sub, only users connected to a specific instance will be able to exchange chat messages with each other. Redis Pub/Sub solves this problem by providing a way to broadcast messages to all the connected users, regardless of the application instance they’re connected to.

Code explanation#

Let’s walk through the application’s logic.

Imports#

Here are some of the package imports:

Imports
  • Line 4: In the import section, we import gorilla/websocket, which provides us the WebSocket server and client implementation in Go.

The init function#

The init function establishes a connection with Redis and is executed before the main function.

The init function
  • Line 1–3: We define the required variables.

  • Line 5: The init function initializes the Users map. It contains the mapping of a user ID (who has joined the chat) to the corresponding *websocket.Conn object.

The main function#

The main function

The main function above is responsible for orchestrating all the functionality. This includes:

  • Line 9: After connecting with Redis and verifying it on lines 3–4, the startChatBroadcaster function is invoked. It further starts a goroutine, which will be covered below.

  • Line 11: The application has a single registered route.

  • Line 16: The server is started.

  • Line 24: After the server is started, the program blocks and waits for the application to exit/terminate.

The rest of the logic in the main function deals with the clean-up process after application exits. These include the following actions:

  • Line 28: Creating a Context, which times out after ten seconds.

  • Lines 32–34: Closing all the user WebSocket connections.

  • Lines 36–37: Unsubscribing and closing the Pub/Sub channel connection.

  • Line 39: Shutting down the server.

The chat HTTP handler#

Now, let’s take a look at the chat HTTP handler in the code below:

The chat HTTP handler

The chat method on line 5 of the code snippet above is an HTTP handler that allows a user to connect to the application over WebSocket and takes care of the user chat session while they are connected.

  • Line 11: First, the connection is upgraded to WebSocket.

  • Line 17: This WebSocket connection is added to the Users map.

  • Lines 20–21: The infinite for loop is used to handle chat messages and terminate the session if the user disconnects (line 27).

  • Line 36: The messages received over WebSocket are sent to the Redis Pub/Sub channel.

The startChatBroadcaster function#

Finally, we have the startChatBroadcaster function:

The startChatBroadcaster function
  • Line 2: The startChatBroadcaster function starts a goroutine.

  • Line 4: The goroutine subscribes to the Redis Pub/Sub channel.

  • Lines 9–11: The goroutine receives messages and broadcasts them to all the connected users over the WebSocket connection.

Application testing#

You can take a look at the complete code for the application. Click the “Run” button in the code widget below to start the application:

/
main.go
Chat application logic

Once that’s done, click + to open a new terminal tab and follow the instructions below to execute commands and test the application using curl.

Change to right directory:

Change to the right directory

Join the chat service as user1:

Add user1 to the chat service

You should see an output along with the prompt (>).

In order to simulate another user (e.g. user2), click + to open a another terminal:

Add user2 to the chat service

Let’s go back to the terminal where we started the application. We should see the following logs:

Application logs

Now there are two users who have joined the service. They can exchange messages. To send and view these messages, perform the following actions:

  1. Send a message from user1, switch to the terminal where we joined the chat service as user1 and type in a text message, for example, Hello, how are you?

  2. Switch to the terminal where we joined the chat service as user2. We should see the message that user1 sent.

  3. From the same terminal, send a message as user2, for example, Doing fine, thank you!

  4. Switch to the terminal where we joined the chat service as user1. We should see the message that user2 sent.

You can continue to learn and experiment with the chat application by adding more users. Finally, to exit the application, press “CTRL + C” in the first terminal where the application was started.

Summary#

The demonstrated chat interactions are happening in real-time using a combination of Redis Pub/Sub and WebSocket. While a chat service was used as an example, the concepts covered in this lesson can be applied to real-time collaborative applications in general.

Implement Worker Queues with Lists

Stream Processing with Redis Streams